package go_api

import (
	"bsfswagger/base"
	"bytes"
	"fmt"
	"html/template"
	"os"
	"strings"
)

type GoTemplate struct {
	Pkg              string
	OutDir           string
	Api              *base.Api
	ReplaceTypeNames []string
}

func (m *GoTemplate) Render() {
	for _, replace := range strings.Split(".,-", ",") {
		m.Pkg = strings.Replace(m.Pkg, replace, "_", -1)
	}
	buf := bytes.Buffer{}
	tmpl := base.TemplateUtil.TryGetTemplateFromCache("api.tmpl", []string{"go_api/api.tmpl"})
	base.TemplateUtil.RenderTemplate(&buf, "api.tmpl", tmpl, map[string]any{
		"api": *m.Api,
		"m":   m,
		"pkg": m.Pkg,
	})
	dir := m.OutDir + "/" + m.Pkg
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		panic(err)
	}
	err = os.WriteFile(dir+"/"+m.Pkg+".go", buf.Bytes(), 0644)
	if err != nil {
		panic(err)
	}
}
func (m *GoTemplate) ClientName() string {
	return m.upperFirstLetter(base.CommonUtil.UnderScoreToCamelCase(m.Pkg)) + "Client"
}
func (m *GoTemplate) ModelName(modelName string) string {
	r := modelName
	r = base.CommonUtil.ReplaceStrs(r, strings.Split("-,.", ","), "_")
	r = base.CommonUtil.ReplaceStr(r, "__", "_")
	r = base.CommonUtil.TrimStr(r, "_")
	for _, replace := range m.ReplaceTypeNames {
		r = base.CommonUtil.ReplaceStr(r, replace, "")
	}
	return m.upperFirstLetter(base.CommonUtil.UnderScoreToCamelCase(r))
}

func (m *GoTemplate) ModelRemark(model base.Model) string {
	var r strings.Builder
	r.WriteString(fmt.Sprintf("//%v\n", m.ModelName(model.Name)))
	if model.Name != "" {
		r.WriteString(fmt.Sprintf("//@Name %v\n", model.Name))
	}
	if model.Description != "" {
		r.WriteString(fmt.Sprintf("//@Description %v\n", model.Description))
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *GoTemplate) PropertiesStr(model base.Model) string {
	var r strings.Builder
	for _, p := range model.Properties {
		r.WriteString(fmt.Sprintf("    %v %v //%v example(%v)\n", m.upperFirstLetter(p.Name), m.getPropertyType(p), p.Description, p.Example))
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *GoTemplate) getPropertyType(property base.Property) string {
	return m.toGoType(property.Type, property.IsArray)
}

func (m *GoTemplate) MethodName(methodName string) string {
	r := methodName
	r = base.CommonUtil.ReplaceStrs(r, strings.Split("{,},/,?,&,-,.", ","), "_")
	r = base.CommonUtil.ReplaceStr(r, "__", "_")
	r = base.CommonUtil.TrimStr(r, "_")
	return m.upperFirstLetter(base.CommonUtil.UnderScoreToCamelCase(r))
}

func (m *GoTemplate) MethodRemark(method base.Method) string {
	var r strings.Builder
	r.WriteString(fmt.Sprintf("//%v\n", m.MethodName(method.Path)))
	r.WriteString(fmt.Sprintf("//@UrlPath %v [%v]\n", method.Path, method.HttpMethod))
	if method.Summary != "" {
		r.WriteString(fmt.Sprintf("//@Summary %v\n", method.Summary))
	}
	if method.Description != "" {
		r.WriteString(fmt.Sprintf("//@Description %v\n", method.Description))
	}
	for _, p := range method.Parameters {
		r.WriteString(fmt.Sprintf("//@Param %v  %v default(%v) required(%v) //%v\n", p.Name, m.getParameterType(p), p.Default, p.Required, p.Description))
	}
	for _, p := range method.Returns {
		if p.Code == "200" {
			r.WriteString(fmt.Sprintf("//@return %v %v //%v\n", p.Name, m.getReturnType(p), p.Description))
		}
	}
	return strings.TrimRight(r.String(), "\n")
}

func (m *GoTemplate) Request(method base.Method) string {
	var r0 strings.Builder
	returnStr := m.ReturnStr(method)
	if returnStr != "" {
		r0.WriteString(fmt.Sprintf("var r %v\n\t", strings.TrimLeft(returnStr, "*")))
	}
	var r strings.Builder
	r.WriteString(fmt.Sprintf("\"Title\":%#v,", m.Api.Title))
	r.WriteString(fmt.Sprintf("\"BasePath\":%#v,", m.Api.BasePath))
	r.WriteString(fmt.Sprintf("\"HttpMethod\":%#v,", method.HttpMethod))
	r.WriteString(fmt.Sprintf("\"Path\":%#v,", method.Path))
	if returnStr != "" {
		r.WriteString("\"Return\":&r,")
	}
	if len(method.Consumes) > 0 {
		r.WriteString(fmt.Sprintf("\"Consumes\":%#v,", method.Consumes))
	}
	if len(method.Produces) > 0 {
		r.WriteString(fmt.Sprintf("\"Produces\":%#v,", method.Produces))
	}
	paramTypes := []string{"path", "query", "body", "formData"}
	for _, paramType := range paramTypes {
		var r2 strings.Builder
		for _, p := range method.Parameters {
			if strings.ToLower(p.In) == strings.ToLower(paramType) {
				r2.WriteString(fmt.Sprintf("\"%v\":%v,", p.Name, p.Name))
			}
		}
		paramStr := strings.TrimRight(r2.String(), ",")
		if paramStr != "" {
			r.WriteString(fmt.Sprintf("\"%vParams\":map[string]any{%v},", m.upperFirstLetter(paramType), paramStr))
		}
	}
	r0.WriteString(fmt.Sprintf("apiRequest:=map[string]any{%v\t}\n", strings.TrimRight(r.String(), ",")))
	r0.WriteString("\tm.ApiClient.ExecuteWithRetry(apiRequest)\n")
	if returnStr != "" {
		if strings.HasPrefix(returnStr, "*") {
			r0.WriteString(fmt.Sprintf("\treturn &r"))
		} else {
			r0.WriteString(fmt.Sprintf("\treturn r"))
		}
	}
	return r0.String()
}

func (m *GoTemplate) ParamsStr(method base.Method) string {
	var r strings.Builder
	for _, p := range method.Parameters {
		r.WriteString(fmt.Sprintf("%v %v,", p.Name, m.getParameterType(p)))
	}
	return strings.TrimRight(r.String(), ",")
}

func (m *GoTemplate) getParameterType(parameter base.Parameter) string {
	return m.toGoType(parameter.Type, parameter.IsArray)
}

func (m *GoTemplate) ReturnStr(method base.Method) string {
	for _, p := range method.Returns {
		if p.Code == "200" {
			return fmt.Sprintf("%v", m.getReturnType(p))
		}
	}
	return ""
}

func (m *GoTemplate) getReturnType(parameter base.ReturnParameter) string {
	return m.toGoType(parameter.Type, parameter.IsArray)
}

func (m *GoTemplate) upperFirstLetter(str string) string {
	if len(str) == 0 {
		return str
	}
	return strings.ToUpper(string(str[0])) + str[1:]
}

func (m *GoTemplate) toGoType(typeName string, isArray bool) string {
	typeNameStr := ""
	isPointer := false
	switch typeName {
	case "string":
		typeNameStr = "string"
	case "integer":
		typeNameStr = "int64"
	case "number":
		typeNameStr = "float64"
	case "boolean":
		typeNameStr = "bool"
	case "":
		typeNameStr = "any"
	case "object":
		typeNameStr = "any"
	default:
		isPointer = true
		typeNameStr = m.ModelName(typeName)
	}
	if isArray {
		return "[]" + typeNameStr
	} else {
		if isPointer {
			return "*" + typeNameStr
		} else {
			return typeNameStr
		}
	}
}

func (m *GoTemplate) Html(html string) template.HTML {
	return template.HTML(html)
}
